package com.basker.pisces.common.sign;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Function;

import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import com.alibaba.fastjson.JSON;
import com.basker.pisces.common.enums.SignatureAlgorithmType;
import com.basker.pisces.exception.ExceptionFactory;

/**
 * 签名生成器,将参数（Map或POJO）的属性按字母排序后拼接起来，按指定算法加密。根据场景不同，有若干参数控制一些细节，比如是否需要追加时间戳、appId等。
 *
 * @author hangwen
 */
public class SignGenerator {

    private SignatureAlgorithmType algorithmType;
    private boolean splitKeyValue = true;
    private String appId;
    private Long timestamp;
    private Object argument;
    private boolean sortKeys = true;
    private boolean ignoreNull = true;
    private Function<String, String> keyHandler;
    private Function<Object, Object> valueHandler;
    private Function<String, String> signHandler;

    /**
     * 签名生成器
     *
     * @param algorithmType                       加密算法
     * @param splitKeyValue                       签名时，各个属性是否用&隔开，key和value是否用=隔开
     * @param appId                               如果有值,appId会拼接到签名最后
     * @param timeStamp                           如果有值,timeStamp会拼接到签名最后（有appId的话，会在其前面）
     * @param argument                            业务参数，可以是Map，也可以是一个POJO对象
     * @param sortKeys,拼接签名前，是否需要将argument的属性集合排序
     * @param ignoreNull                          对于value为null的属性，是否忽略，如果不忽略则值用空字符串代替
     * @param keyHandler                          key处理器
     * @param valueHandler                        value处理器
     * @param signHanlder                         sign处理器，加密前调用
     */
    public SignGenerator(SignatureAlgorithmType algorithmType, boolean splitKeyValue, @Nullable String appId,
                         @Nullable Long timestamp, Object argument, boolean sortKeys, boolean ignoreNull,
                         @Nullable Function<String, String> keyHandler, @Nullable Function<Object, Object> valueHandler,
                         @Nullable Function<String, String> signHandler) {
        Assert.notNull(algorithmType, "parameter 'algorithmType' is required");
        Assert.notNull(argument, "parameter 'argument' is required");

        this.algorithmType = algorithmType;
        this.splitKeyValue = splitKeyValue;
        this.appId = appId;
        this.timestamp = timestamp;
        this.argument = argument;
        this.sortKeys = sortKeys;
        this.ignoreNull = ignoreNull;
        this.keyHandler = keyHandler;
        this.valueHandler = valueHandler;
        this.signHandler = signHandler;
    }

    /**
     * 签名生成器
     *
     * @param algorithmType 加密算法
     * @param appId         如果有值,appId会拼接到签名最后
     * @param timeStamp     如果有值,timeStamp会拼接到签名最后（有appId的话，会在其前面）
     * @param argument      业务参数，可以是Map，也可以是一个POJO对象
     */
    public SignGenerator(SignatureAlgorithmType algorithmType, @Nullable String appId, @Nullable Long timestamp,
                         Object argument) {
        this(algorithmType, true, appId, timestamp, argument, true, true, null, null, null);
    }

    public String generate() {
        try {
            return this.generate0();
        } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
            throw ExceptionFactory.createRuntimeException(e, "pisces-common.SignGenerator.generate-001",
                    "exception occur when sign generate:{0}", e.getMessage());
        }
    }

    @SuppressWarnings("unchecked")
    private String generate0() throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
        StringBuilder sign = new StringBuilder();

        Map<String, Object> map = null;

        if (this.argument instanceof Map<?, ?>) {
            map = (Map<String, Object>) this.argument;
        } else if (this.argument != null) {
            String text = JSON.toJSONString(this.argument);
            map = JSON.parseObject(text);
        }

        if (!CollectionUtils.isEmpty(map)) {
            List<String> keys = new ArrayList<>(map.keySet());
            if (this.sortKeys) {
                Collections.sort(keys);
            }

            for (String key : keys) {
                Object val = map.get(key);

                if (val == null) {
                    if (ignoreNull) {
                        continue;
                    } else {
                        val = "";
                    }
                }

                if (this.keyHandler != null) {
                    key = this.keyHandler.apply(key);
                }

                if (this.valueHandler != null) {
                    val = this.valueHandler.apply(val);
                }

                if (splitKeyValue) {
                    sign.append(key).append("=").append(val).append("&");
                } else {
                    sign.append(key).append(val);
                }
            }
        }

        if (splitKeyValue) {
            sign.deleteCharAt(sign.length() - 1);
        }

        if (timestamp != null) {
            sign.append(timestamp);
        }

        if (appId != null) {
            sign.append(appId);
        }

        String sign2String = sign.toString();
        if (signHandler != null) {
            sign2String = signHandler.apply(sign2String);
        }

        if (SignatureAlgorithmType.MD5 == algorithmType) {
            return DigestUtils.md5Hex(sign2String);
        } else {
            return DigestUtils.sha1Hex(sign2String);
        }
    }

}
